#!/usr/bin/python

"""
 Module: getlastfminfo.py
  Ver: 2.1; Rev Last 08/28/2012, Previous 08/21/2012

 Rev 2.0; Last: 01/18/2012 - Previous: 12/26/2011 (AEM)
   - Minor bug fixes
   - Add cfg file values

 Rev 2.1; 08/28/2012 - 08/21/2012 (AEM)
   - Add runtime logging
   - Remove remaining (almost) debug code

 Rev 2.2.1; Last: 02/12/2013 - Previous: 02/11/2013  (AEM)
   - Clean up to match PEP8 style guide as closely as practical
   - Clean up to match pylint style guide as closely as practical

 Dependencies:
     - Install ElementTree library. See opening comments in showtags.ph
       for instructions.

 Required local settings:
     - self.myAPIKey = "your_registered_Last.FM_API_Key"
       Value is assigned from the config file

     - self.xmlSavePath = 'your_xmlfiles_save_path'
       Value is assigned from the config file

 Copyright (C) 2010-2012  Arnold E. Morgan

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details, http://www.gnu.org/licenses/.

"""

from xml.etree import ElementTree as ET
import urllib
import sys
import os.path
import logging


class GetLastFM:
   """
       Methods to retrieve track and album xml data from Last.fm or
       an existing file.
   """

   def __init__(self, thedebuglevel, theAPIKey, theRootURL, theXMLPath):

      self.DebugLevel = thedebuglevel

      #*** Default xml save file dir ***
      self.xmlSavePath = theXMLPath

      #*** Last.fm user specific web API key ***
      self.myAPIKey = theAPIKey

      self.APIrootURL = theRootURL

      self.APITrackInfo = '?method=track.getinfo'
      self.APIAlbumInfo = '?method=album.getinfo'

      self.Genre = "?"
      self.AlbumName = "?"
      self.AlbumImage = "?"
      self.myComment = "?"
      self.ReleaseYear = "?"
      self.TrackErrMssg = "ok"
      self.AlbumErrMssg = "ok"
      self.xmlgetTrackStatus = "?"
      self.xmlgetAlbumStatus = "?"
      self.FailFlag = False

      self.ArtistName = "?"
      self.TrackName = "?"

   def get_track_info_from_last_fm(self, theArtist, theTrack):
      """ Initiate a track request to last.fm.

          Build the track request with artist and track name and API key.
          Submit the URL request and assign the response to an ElementTree
          structure.

          Method get_track_info parses xml branches needed and assigns
          relevant values to class variables needed to update the UI.

          In the process of retrieving data from Last.fm, save the results in
          a local xml file. This file will be used later to display the
          album image. It is also recommended by Last.fm to cache results
          locally to ease pressure on their server resources.

      """

      self.ArtistName = theArtist
      self.TrackName = theTrack
      self.FailFlag = False

      outXMLFile = self.build_track_file_name()

      if self.DebugLevel >= 1:
         logging.info("Track OUT File: %s" % outXMLFile)

      TrackRequest = "%s%s&api_key=%s&artist=%s&track=%s" % \
                     (self.APIrootURL, self.APITrackInfo, self.myAPIKey,
                     self.ArtistName, self.TrackName)

      if self.DebugLevel >= 1:
         logging.info(TrackRequest)

      try:
         urllib.socket.setdefaulttimeout(8)
         usock = urllib.urlopen(TrackRequest)
         #*** Future Note: From Python lib: IOError exception
         #    if urlopen fails ***
         self.tree = ET.parse(usock)
         usock.close()
         self.tree.write(outXMLFile)
      except Exception, ex:
         self.FailFlag = True
         self.xmlgetTrackStatus = "Track URL Read Fail"
         #self.TrackErrMssg = "Captured Error: " + str(ex)
         self.TrackErrMssg = "Socket Error: " + self.xmlgetTrackStatus + \
                             "--" + str(ex)
      except:
         self.FailFlag = True
         self.xmlgetTrackStatus = "Track URL Read Fail"
         self.TrackErrMssg = "\nUnexpected Error: " + \
                             self.xmlgetTrackStatus + "--" + sys.exc_info()[0]

      if not self.FailFlag:
         if self.DebugLevel >= 1:
            logging.info("TRACK URL READ SUCCESSFUL!")

         #*** Now call the common method get_track_info to do the
         #    xml file parsing ***
         self.get_track_info()

         if self.DebugLevel > 0:
            #self.ShowTrackInfo()
            self.show_track_info()

   def get_track_info_from_file(self, theArtist, theTrack):
      """
          Initiate a track request from a locally saved file if one is
          available.

          Assign the requested file if it exists to an ElementTree structure.

          Method get_track_info parses xml branches needed and assigns
          relevant values to class variables needed to update the UI.

      """

      self.ArtistName = theArtist
      self.TrackName = theTrack
      self.FailFlag = False

      inXMLFile = self.build_track_file_name()

      if self.DebugLevel >= 1:
         logging.info("Track IN File: %s" % inXMLFile)

      if not os.path.exists(inXMLFile) or not os.path.isfile(inXMLFile):
         self.FailFlag = True
         self.xmlgetTrackStatus = "File Exists Fail"
         self.TrackErrMssg = "Track file path %s not found" % inXMLFile
      else:
         try:
            self.tree = ET.parse(inXMLFile)
         except Exception, ex:
            self.FailFlag = True
            self.xmlgetTrackStatus = "Track File Read Fail"
            self.TrackErrMssg = "Captured Error: " + \
                                self.xmlgetTrackStatus + "--" + str(ex)
         except:
            self.FailFlag = True
            self.xmlgetTrackStatus = "Track File Read Fail"
            self.TrackErrMssg = "\nUnexpected Error: " + \
                                self.xmlgetTrackStatus + "--" + \
                                sys.exc_info()[0]

      if not self.FailFlag:
         if self.DebugLevel >= 1:
            logging.info("TRACK IN FILE READ SUCCESSFUL!")

         #*** Now call the common method get_track_info to do the xml
         #    file parsing. ***
         self.get_track_info()

         if self.DebugLevel >= 1:
            self.show_track_info()

   def build_track_file_name(self):
      FileArtistName = self.ArtistName.replace(" ", "_")
      FileTrackName = self.TrackName.replace(" ", "_")

      tmpXMLFile = "%s_%sTR.xml" % (FileArtistName.lower(),
                                    FileTrackName.lower())
      return self.xmlSavePath + tmpXMLFile

   def build_album_file_name(self):
      FileArtistName = self.ArtistName.replace(" ", "_")
      FileAlbumName = self.AlbumName.replace(" ", "_")
      tmpXMLFile = "%s_%sAL.xml" % (FileArtistName.lower(),
                                    FileAlbumName.lower())
      return self.xmlSavePath + tmpXMLFile

   def get_track_info(self):
      """ Extract track related items from the xml structure.

      This method is common to both track file read and Last.FM download.

      """

      myroot = self.tree.getroot()
      self.xmlgetTrackStatus = myroot.attrib.get("status")

      #*** Error status (if exists) ***
      self.TrackErrMssg = "ok"
      for EMssg in self.tree.findall("//error"):
         self.TrackErrMssg = EMssg.text

      #print "Root tag: %s" % (myroot.tag)
      #print "Entire tree: \n%s" % (ET.tostring(myroot))

      # Get Musical Genre
      self.Genre = "?"
      for mtype in self.tree.findall("//toptags/tag/name"):
         # Populate the genre value only with the first xml value found.
         # There are often more than one of these tags sequentially.
         # Empirically the first one is usually the best.
         if len(self.Genre) < 2:     # 01/12/2012 -- was init to "?"
            self.Genre = mtype.text  # "if" test no longer makes sense

      # Get Album Name
      self.AlbumName = "?"
      for Atitle in self.tree.findall("//title"):
         self.AlbumName = Atitle.text

      #*** Get Album Image ***
      self.AlbumImage = "?"
      for Aimage in self.tree.findall("//image"):
         if Aimage.attrib.get("size") == "large":
            self.AlbumImage = Aimage.text

      self.myComment = "?"
      for mcomm in self.tree.findall("//track/url"):
         #if len(theComment) < 2:
         self.myComment = mcomm.text

   def show_track_info(self):
      #*** This method is mostly for debug purposes ***
      logging.debug("Track Info - Status: %s Error Status: **%s**, "
                    "Genre: %s" %
                    (self.xmlgetTrackStatus, self.TrackErrMssg, self.Genre))

      logging.debug("Track Info - Album Name: %s Image URL: %s "
                    "Comment: %s" %
                    (self.AlbumName, self.AlbumImage, self.myComment))

   def get_album_info_from_last_fm(self, theArtist, theAlbum):
      """ Initiate an album request to last.fm.

      Build the album request with artist and album name and API key.
      Submit the URL request and assign the response to an ElementTree
      structure.

      Method GetAlbumInfo parses xml branches needed and assigns relevant
      values to class variables needed to update the UI.

      In the process of retrieving data from Last.fm, save the results in
      a local xml file. This file will be used later to display the
      album image. It is also recommended by Last.fm to cache results
      locally to ease pressure on their server resources.

      """

      self.ArtistName = theArtist
      self.AlbumName = theAlbum
      self.FailFlag = False

      outXMLFile = self.build_album_file_name()

      if self.DebugLevel >= 1:
         logging.info("Album OUT File: %s" % outXMLFile)

      AlbumRequest = "%s%s&api_key=%s&artist=%s&album=%s" % \
                     (self.APIrootURL, self.APIAlbumInfo, self.myAPIKey,
                     self.ArtistName, self.AlbumName)

      if self.DebugLevel >= 1:
         logging.info(AlbumRequest)

      try:
         urllib.socket.setdefaulttimeout(8)
         usock = urllib.urlopen(AlbumRequest)
         self.tree = ET.parse(usock)
         usock.close()
         self.tree.write(outXMLFile)
      except Exception, ex:
         self.FailFlag = True
         self.xmlgetAlbumStatus = "URL Read Fail"
         self.AlbumErrMssg = "\nCaptured Error: " + str(ex)
      except:
         self.FailFlag = True
         self.xmlgetAlbumStatus = "URL Read Fail"
         self.AlbumErrMssg = "\nUnexpected Error: ", sys.exc_info()[0]

      if not self.FailFlag:
         if self.DebugLevel >= 1:
            logging.info("ALBUM URL READ SUCCESSFUL!")

         self.get_album_info()
         #self.show_all_tree()

         if self.DebugLevel >= 1:
            self.show_album_info()

   def get_album_info_from_file(self, theArtist, theAlbum):
      """
          Initiate an album request from a locally saved file if one exists.

          Assign the requested file if it exists to an ElementTree structure.

          Method GetAlbumInfo parses xml branches needed and assigns relevant
          values to class variables needed to update the UI.

      """

      self.ArtistName = theArtist
      self.AlbumName = theAlbum
      self.FailFlag = False

      inXMLFile = self.build_album_file_name()

      if self.DebugLevel >= 1:
         logging.info("Album IN File: %s" % inXMLFile)

      if not os.path.exists(inXMLFile) or not os.path.isfile(inXMLFile):
         self.FailFlag = True
         self.xmlgetAlbumStatus = "File Exists Fail"
         self.AlbumErrMssg = "Album file path %s not found" % inXMLFile
      else:
         try:
            self.tree = ET.parse(inXMLFile)
         except Exception, ex:
            self.FailFlag = True
            self.xmlgetAlbumStatus = "File Read Fail"
            self.AlbumErrMssg = "\nCaptured Error: " + str(ex)
         except:
            self.FailFlag = True
            self.xmlgetAlbumStatus = "File Read Fail"
            self.AlbumErrMssg = "\nUnexpected Error: ", sys.exc_info()[0]

      if not self.FailFlag:
         if self.DebugLevel >= 1:
            logging.info("ALBUM IN FILE READ SUCCESSFUL!")

         self.get_album_info()
         #self.show_all_tree()

         if self.DebugLevel >= 1:
            self.show_album_info()

   def get_album_info(self):
      """ Extract album related items from the xml structure.

      This method is common to both album file read and last.fm download.

      """

      myroot = self.tree.getroot()
      self.xmlgetAlbumStatus = myroot.attrib.get("status")

      #*** Error status (if exists) ***
      self.AlbumErrMssg = "ok"
      for EMssg in self.tree.findall("//error"):
         self.AlbumErrMssg = EMssg.text

      # Get Release Date
      self.ReleaseYear = "?"
      for Adate in self.tree.findall("//releasedate"):
         self.ReleaseYear = Adate.text

   def show_album_info(self):
      #*** This method is mostly for debug purposes ***
      logging.info("Album Info - Status: %s Error Status: %s "
                   "Release Date: %s" %
                   (self.xmlgetAlbumStatus, self.AlbumErrMssg,
                   self.ReleaseYear))

   def show_all_tree(self):
      #*** This method is mostly for debug purposes ***
      myiter = self.tree.getiterator()
      for myelement in myiter:
         logging.debug("%s - %s" % (myelement.tag, myelement.text))


if __name__ == "__main__":
   #*** This is all debug, test stuff ***
   myTrackInfo = GetLastFM(1)

   #myTrackInfo.get_track_info_from_file("Boney James", "Wait For Love")

   myTrackInfo.get_track_info_from_last_fm("Daryl Hall", "She's Gone")
   print "Track GET STATUS: %s, Error Status: %s" % \
         (myTrackInfo.xmlgetTrackStatus, myTrackInfo.TrackErrMssg)

   #myTrackInfo.get_track_info_from_last_fm("Richard Elliot",
   #                                        "Still Sweet on You")

   #myTrackInfo.get_album_info_from_file("Richard Elliot", "Crush")

   #print "Album GET STATUS: %s\n, Error Status: %s\n, "\
   #       Album Release Date: %s" % \
   #                 (myTrackInfo.xmlgetAlbumStatus, myTrackInfo.AlbumErrMssg,
   #                  myTrackInfo.ReleaseYear)

   #myTrackInfo.get_album_info_from_last_fm("ZChuck Loeb","The Music Inside")

   #print "Album GET STATUS: %s, Error Status: %s, Album Release Date: %s" % \
   #                 (myTrackInfo.xmlgetAlbumStatus, myTrackInfo.AlbumErrMssg,
   #                  myTrackInfo.ReleaseYear)

   print "Done"
